package bubbleindex;

import com.nativelibs4java.opencl.CLBuffer;
import com.nativelibs4java.opencl.CLContext;
import com.nativelibs4java.opencl.CLEvent;
import com.nativelibs4java.opencl.CLKernel;
import com.nativelibs4java.opencl.CLMem;
import com.nativelibs4java.opencl.CLQueue;
import java.nio.ByteOrder;
import java.util.concurrent.Callable;
import org.bridj.Pointer;
import static org.bridj.Pointer.allocateFloats;

/**
 *
 * @author ttrott
 */
public class MyCallable implements Callable<Float> {
     
    private final int NumberOfDays;
    private final int j;
    private final CLQueue clqueue;
    private final CLContext clcontext;
    private final CLKernel claddFloatsKernel;
    
    public MyCallable (CLContext context, CLKernel addFloatsKernel, CLQueue queue, 
            int index, final int number) {
        NumberOfDays = number;
        j = index;
        clqueue = queue;
        clcontext = context;
        claddFloatsKernel = addFloatsKernel;
    }
    
    /**
     * This is a callable task to calculate the HQ derivative value of a 
     * given time series.
     * @return The HQ Derivative value, or The Value of the Bubble Index 
     */
    
    @Override
    public Float call() {
                
        if (!BuildIndex.Stop) {
            
            int FREQ_SIZE = 70;
            int Q_SIZE = 18;
            int H_SIZE = 19;

            ByteOrder byteOrder = clcontext.getByteOrder();

            Pointer<Float> Values = allocateFloats(Q_SIZE * H_SIZE).order(byteOrder);
            Pointer<Float> timevalues = allocateFloats(NumberOfDays).order(byteOrder);
            Pointer<Float> logtimeValues = allocateFloats(NumberOfDays).order(byteOrder);
            Pointer<Float> testfreq = allocateFloats(FREQ_SIZE).order(byteOrder);
            Pointer<Float> q = allocateFloats(Q_SIZE).order(byteOrder);
            Pointer<Float> h = allocateFloats(H_SIZE).order(byteOrder);
            Pointer<Float> hqderiv = allocateFloats(NumberOfDays * (Q_SIZE * H_SIZE + 1)).order(byteOrder);

            //Initialize important values
            float Omega_float = (float)BubbleIndex.Omega;
            float M_float = (float)BubbleIndex.M;
            float StartingPoint = (float) (Omega_float / (2.0f * 3.14159f) - 0.2f);
            float Increments = 0.01f;

            //Initialize important arrays
            for (int i = 0; i < FREQ_SIZE; i++) {
                testfreq.set(i, (float) StartingPoint + i * Increments);
            }

            StartingPoint = 0.1f;
            Increments = 0.05f;
            for (int i = 0; i < Q_SIZE; i++) {
                q.set(i, (float) StartingPoint + i * Increments);
            }

            StartingPoint = -1.0f * 0.9f;
            Increments = 0.1f;
            for (int i = 0; i < H_SIZE; i++) {
                h.set(i, (float) StartingPoint + i * Increments);
            }        

            double[] TimeValues = new double[NumberOfDays];
            double[] TimeValues_M_Power = new double[NumberOfDays];
            double[] LogCosTimeValues = new double[NumberOfDays];
            double[] SelectedData = new double[NumberOfDays];
            double[] Coef = new double[3];

            //Arrays used in the H,Q derivative calculation and periodogram
            for (int k = 0; k < NumberOfDays; k++) {
                TimeValues[k] = NumberOfDays + BubbleIndex.T_crit - k;
                TimeValues_M_Power[k] = Math.pow(TimeValues[k], 
                        M_float);
                LogCosTimeValues[k] = Math.cos(Omega_float * 
                    Math.log(TimeValues[k])) * TimeValues_M_Power[k];
                SelectedData[k] = BubbleIndex.DailyPriceValues[k + j + 1];        
            }

            //Normalize data to a price starting at 100
            BuildIndex.Normalize(SelectedData, NumberOfDays);

            BuildIndex.DataReverse(SelectedData, NumberOfDays);

            //Fit the curve with the equation given in:
            BuildIndex.LinearFit(SelectedData, TimeValues_M_Power, LogCosTimeValues,
                        Coef, NumberOfDays);

            //Prepare the arrays to be copied to device memory
            for (int i = 0; i < NumberOfDays; i++) {
                timevalues.set(i, (float)TimeValues[i]);
            }

            for (int i = 0; i < NumberOfDays; i++) {
                logtimeValues.set(i, (float) Math.log(timevalues.get(i)));
            }

            for (int i = 0; i < Q_SIZE * H_SIZE; i++) {
                Values.set(i, 0.0f);
            }       

            for (int i = 0; i < NumberOfDays * (Q_SIZE * H_SIZE + 1); i++) {
                hqderiv.set(i, 0.0f);
            }

            // Create OpenCL input buffers (using the native memory pointers) :
            CLBuffer<Float> 
                values = clcontext.createFloatBuffer(CLMem.Usage.InputOutput, Values),
                LogTimeValues = clcontext.createFloatBuffer(CLMem.Usage.Input, logtimeValues),
                TimeValuesFinal = clcontext.createFloatBuffer(CLMem.Usage.Input, timevalues),                
                TestFrequencies = clcontext.createFloatBuffer(CLMem.Usage.Input, testfreq),
                Q = clcontext.createFloatBuffer(CLMem.Usage.Input, q),
                H = clcontext.createFloatBuffer(CLMem.Usage.Input, h),
                HQDerivativeData = clcontext.createFloatBuffer(CLMem.Usage.InputOutput, hqderiv);

            Values.release();
            logtimeValues.release();
            timevalues.release();
            testfreq.release();
            q.release();
            h.release();
            hqderiv.release();

            float Temp = 0.0f;
            final float output;

            Pointer<Float> outPtr = allocateFloats(Q_SIZE * H_SIZE).order(byteOrder);
            
            for (int i = 0; i < Q_SIZE * H_SIZE; i++) {
                outPtr.set(i, 0.0f);
            }
            
            CLEvent addEvt;
            synchronized (claddFloatsKernel) {
                claddFloatsKernel.setArg(0, values);
                claddFloatsKernel.setArg(1, LogTimeValues);
                claddFloatsKernel.setArg(2, TimeValuesFinal);
                claddFloatsKernel.setArg(3, TestFrequencies);
                claddFloatsKernel.setArg(4, HQDerivativeData);
                claddFloatsKernel.setArg(5, Q);
                claddFloatsKernel.setArg(6, H);
                claddFloatsKernel.setArg(7, M_float);
                claddFloatsKernel.setArg(8, Omega_float);
                claddFloatsKernel.setArg(9, (float)Coef[1]);
                claddFloatsKernel.setArg(10, (float)Coef[2]);
                claddFloatsKernel.setArg(11, Q_SIZE);
                claddFloatsKernel.setArg(12, H_SIZE);
                claddFloatsKernel.setArg(13, FREQ_SIZE);
                claddFloatsKernel.setArg(14, NumberOfDays);

	        int[] globalSizes = new int[] {Q_SIZE , H_SIZE};
	        addEvt = claddFloatsKernel.enqueueNDRange(clqueue, globalSizes);
            }
           
            outPtr = values.read(clqueue, addEvt); 

            values.release();
            LogTimeValues.release();
            TimeValuesFinal.release();
            HQDerivativeData.release();
            TestFrequencies.release();
            Q.release();
            H.release();                
            
            for (int i = 0; i < Q_SIZE * H_SIZE; i++) {
                if (outPtr.get(i) > Temp) {
                    Temp = outPtr.get(i);
                }
            }
            
            outPtr.release();
            output = Temp;
            
            final int DisplayPeriod = j + NumberOfDays;
            final int extra = NumberOfDays;
            final String name = BubbleIndex.SelectionName;
            final String DisplayPeriodString = BubbleIndex.DailyPriceDateList.get(DisplayPeriod);
            BubbleIndex.displayOutput("Name: " + name + " Date: " + DisplayPeriodString +
                            " Value: " + output + " Length: " + extra, false);

            return Temp;                
        }
                
        else {            
            return 0.0f;        
        }
    }
}
  
   